(ns rete.predicate
  (:require [clojure.string :as string])
  (:require [rete.core :refer :all])
  (:require [rete.node :refer :all :as rete-node])
  (:require [clojure.string :as string])
  (:require [clojure.set :as set])
  (:import [rete.core Environment Graph]))

(defn- count-complied-objects
  [node-name ^Environment env]
  (count (:complied-objects (get-node (:graph env) node-name))))

(defn complied-objects-equals
  "(complied-objects-equals node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node equals the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' = " (str value)])
                                   (fn[object ^Environment env](= (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-equals-not
  "(complied-objects-equals-not node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node equals not the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' <> " (str value)])
                                   (fn[object ^Environment env](not= (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-more-than
  "(complied-objects-more-than node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node is more than the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' > " (str value)])
                                   (fn[object ^Environment env](> (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-more-or-equals
  "(complied-objects-more-or-equals node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node is more than or equals the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' >= " (str value)])
                                   (fn[object ^Environment env](>= (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-less-than
  "(complied-objects-less-than node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node is less than the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' < " (str value)])
                                   (fn[object ^Environment env](< (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-less-or-equals
  "(complied-objects-less-or-equals node-name value) creates a new α-node with a predicate which returns 'true' if the number of complied objects for the specified node is less than or equals the passed value."
  [node-name value]
  {:pre [(number? value)]}
  (update-in (rete-node/new-counting-α-node (string/join ["Count of '" node-name "' <= " (str value)])
                                   (fn[object ^Environment env](<= (count-complied-objects node-name env) value)))
             [:parents] conj node-name ))

(defn complied-objects-contains
  "(complied-objects-contains node-name value) creates a new α-node with a predicate which returns 'true' if the complied objects for the specified node contains the passed object.
Note that the hash value of the passed object is used."
  [node-name object]
  {:pre [(map? object)]}
  (update-in (rete-node/new-α-node (string/join ["Node '" node-name "' contains " (str object)])
                                   (fn[object ^Environment env](contains? (:complied-objects (get-node (:graph env) node-name)) 
                                                                          (hash object))))
             [:parents] conj node-name ))

(defn equals
  ([key value]
    "(equals key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' equals the passed value."
    (rete-node/new-α-node (string/join [(str key) " equals " (str value)])
                          (fn[object ^Environment env](= (get object key nil) value))))
  
  ([key value nil_value]
    "(equals  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which equals the passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " equals " (str value)])
                        (fn[object ^Environment env](if (number? (get object key nil_value)) (= (get object key nil_value) value) false)))))

(defn equals-not 
  ([key value]
    "(equals-not key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' equals not the passed value."
    (rete-node/new-α-node(string/join [(str key) " equals not " (str value)])
                         (fn[object ^Environment env](not= (get object key nil) value))))
  ([key value nil_value]
    "(equals-not  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which does not equalsthe passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " less or equal " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil_value)) (not= (get object key nil_value) value) false)))))


(defn contains 
  "(contains key values) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a collection which contains all the passed values."
  [key values]
  {:pre [(set? values)]}
  (rete-node/new-α-node (string/join [(str key) " contains " (str values)])
              (fn[object ^Environment env](if (coll? (get object key nil)) 
                                            (set/subset? values (if (set? (get object key)) 
                                                                  (get object key)
                                                                  (set (get object key))))
                                            false ))))

(defn text-contains 
  "(text-contains  key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a String which contains the passed value."
  [key value]
  {:pre [(string? value)]}
  (rete-node/new-α-node (string/join ["text" (str key) " contains " (str value)])
              (fn[object ^Environment env](if (string? (get object key nil)) (. (string/lower-case (get object key "")) contains (string/lower-case value)) false))))

(defn more-than
  ([key value]
    "(more-than  key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is more as the passed value."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " more than " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil)) (> (get object key) value) false))))
  ([key value nil_value]
    "(more-than  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is more than as the passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " more than " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil_value)) (> (get object key nil_value) value) false)))))


(defn more-or-equal
  ([key value]
    "(more-or-equal  key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is more or euqal as the passed value."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " more or equal " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil)) (>= (get object key) value) false))))
  ([key value nil_value]
    "(more-or-equal  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is more or euqal as the passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " more or equal " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil_value)) (>= (get object key nil_value) value) false)))))


(defn less-than
 ([key value]
   "(less-than  key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is less as the passed value."
  {:pre [(number? value)]}
  (rete-node/new-α-node (string/join [(str key) " less then " (str value)])
              (fn[object ^Environment env](if (number? (get object key nil)) (< (get object key) value) false))))
  ([key value nil_value]
   "(less-than  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is less than as the passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
   {:pre [(number? value)]}
  (rete-node/new-α-node (string/join [(str key) " less then " (str value)])
                        (fn[object ^Environment env](if (number? (get object key nil_value)) (< (get object key nil_value) value) false)))))

(defn less-or-equal
  ([key value]
    "(less-or-equal  key value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is less or euqal as the passed value."
    {:pre [(number? value)]}
    (rete-node/new-α-node (string/join [(str key) " less or equal " (str value)])
                          (fn[object ^Environment env](if (number? (get object key nil)) (<= (get object key) value) false))))

  ([key value nil_value]
   "(less-or-equal  key value nil_value) creates a new α-node with a predicate which returns 'true' if the value for the specified 'key' is a number which is less or euqal as the passed value. 
'nil_value' specifies the value which is returned, if the objects has not the specified key."
   {:pre [(number? value)]}
  (rete-node/new-α-node (string/join [(str key) " less or equal " (str value)])
                        (fn[object ^Environment env](if (number? (get object key nil_value)) (<= (get object key nil_value) value) false)))))

(defn combine-predicates [predicates]
  "(combine-predicates predicates) creates a new α-node which combines the defined predicate into this one node."
  (let [conditions (reduce #(conj %1 (:predicate %2)) #{} predicates )]
    (rete-node/new-α-node (string/join [(string/join " ∧ " (map #(:name %1) predicates))])
                          (fn [object ^Environment env] (reduce #(min %1 (transform-into-truth-value (%2 object env)))
                                                                1 conditions
                                                                )))))